## Transform function

Transform functions allow you to write custom [Elixir](https://elixir-lang.org/) code to transform your messages. This is useful for more complex transformations that are not possible with the path transform, such as:

- Specifying a format that is necessary for your sink destination
- Sanitizing sensitive data, such as PII or payment card data
- Converting timestamp formats
- Adding computed fields
- And much more!

### Function syntax

Every function transform is implemented as an Elixir `transform/4` function:

```Elixir
def transform(action, record, changes, metadata) do
  # Your transform here
end
```

The `transform/4` function receives each key from the [message object](/reference/messages) as an argument:

- `record`: The full record object
- `changes`: The changes object
- `action`: The action type (insert, update, delete)
- `metadata`: The metadata object

<Info>
  Your transform must define the `transform/4` function and may not define any
  other functions.
</Info>

### Elixir standard library

The function transform allows you to use a subset of the Elixir standard library, including:

- [String](https://hexdocs.pm/elixir/String.html)
- [Map](https://hexdocs.pm/elixir/Map.html)
- [Enum](https://hexdocs.pm/elixir/Enum.html)
- [Date](https://hexdocs.pm/elixir/Date.html), [DateTime](https://hexdocs.pm/elixir/DateTime.html), and [NaiveDateTime](https://hexdocs.pm/elixir/NaiveDateTime.html)
- [Kernel](https://hexdocs.pm/elixir/Kernel.html)
- [Decimal](https://hexdocs.pm/decimal/readme.html)
- [URI](https://hexdocs.pm/elixir/URI.html)
- [Base](https://hexdocs.pm/elixir/Base.html)
- [JSON](https://hexdocs.pm/elixir/JSON.html)
- [Integer](https://hexdocs.pm/elixir/Integer.html)
- [Regex](https://hexdocs.pm/elixir/Regex.html)
- [List](https://hexdocs.pm/elixir/List.html)

In addition to the standard library modules, you can also use certain other dependencies:

- [UUID](https://hexdocs.pm/uuid/readme.html)

Other parts of the Elixir standard library and other dependencies are not yet supported. If you need something specific, please [let us know](https://github.com/sequinstream/sequin/issues/new/choose)
and we will be happy to help.

### Examples

Here are some examples of how to use the function transform:

#### Extract the record ID

```Elixir
def transform(action, record, changes, metadata) do
  record["id"]
end
```

#### Format for webhook

```Elixir
# Format the record for ElastiCache with a specific key structure
def transform(action, record, changes, metadata) do
  record_id = record["id"]

  %{
    key: "#{metadata.table_schema}.#{metadata.table_name}.#{record_id}",
    value: record,
    ttl: 3600 # 1 hour TTL
  }
end
```

<Note>
  Both `record` and `changes` use string keys for access (ie. `record["id"]`).

In contrast, the `metadata` object uses atom keys (ie. `metadata.table_schema`).

This is because the `record` and `changes` objects are dynamically typed––they depend on the schema of the connected Postgres table. Metadata, on the other hand, is statically typed and will always have the same keys.

See the [Elixir Map docs](https://hexdocs.pm/elixir/1.12/Map.html) for more information on the difference between string and atom keys.

</Note>

#### Sanitize sensitive data

```Elixir
# Remove or mask sensitive fields
def transform(action, record, changes, metadata) do
  record
  |> Map.drop(["password", "credit_card", "ssn"])
  |> Map.update!("email", fn email ->
    [name, domain] = String.split(email, "@")
    masked_name = String.slice(name, 0, 2) <> String.duplicate("*", String.length(name) - 2)
    masked_name <> "@" <> domain
  end)
end
```

#### Add computed property

```Elixir
# Add a computed full name field
def transform(action, record, changes, metadata) do
  first_name = record["first_name"]
  last_name = record["last_name"]
  full_name = first_name <> " " <> last_name
  name_length = String.length(full_name)

  %{
    full_name: full_name,
    name_length: name_length
  }
end
```

#### Convert timestamp formats

```Elixir
# Convert timestamp formats
def transform(action, record, changes, metadata) do
  timestamp = record["timestamp"]

  %{
    unix_timestamp: DateTime.to_unix(timestamp),
    truncated_timestamp: DateTime.truncate(timestamp, :second),
    iso8601_timestamp: DateTime.to_iso8601(timestamp)
  }
end
```
